/*
 * This file is part of the OWASP Proxy, a free intercepting proxy library.
 * Copyright (C) 2008-2010 Rogan Dawes <rogan@dawes.za.net>
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to:
 * The Free Software Foundation, Inc., 
 * 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 */

package org.owasp.proxy.io;

import java.io.IOException;
import java.io.InputStream;
import java.util.zip.Deflater;


public class GzipInputStream extends DeflaterInputStream {
	protected CRC32InputStream crc;

	/*
	 * GZIP header magic number.
	 */
	private final static int GZIP_MAGIC = 0x8b1f;

	private boolean eof = false;

	private int header = 0;

	private int trailer = -1;

	public GzipInputStream(InputStream in) {
		this(in, 512);
	}

	public GzipInputStream(InputStream in, int size) {
		// this is a bit of a hack. We HAVE to calculate the CRC on
		// uncompressed data, but reading from super gives Deflated data.
		// So wrap the original InputStream in a CRC-calculating InputStream
		// before we call the super.constructor.
		// To get our own reference to it, we have to refer to super.in, and
		// cast it back
		super(new CRC32InputStream(in), new Deflater(
				Deflater.DEFAULT_COMPRESSION, true), size);
		crc = (CRC32InputStream) super.in;
	}

	public int read() throws IOException {
		return super.read();
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.util.zip.DeflaterInputStream#read(byte[], int, int)
	 */
	@Override
	public int read(byte[] b, int off, int len) throws IOException {
		int read = 0;
		if (header < HEADER.length) {
			read += addHeader(b, off, len);
		}
		if (!eof) {
			int got;
			while (read < len
					&& (got = super.read(b, off + read, len - read)) > -1) {
				read += got;
			}
			if (read < len)
				eof = true;
		}
		if (eof)
			if (trailer < TRAILER.length) {
				read += addTrailer(b, off + read, len - read);
			} else {
				return -1;
			}
		return read;
	}

	private int addHeader(byte[] b, int off, int len) {
		int l = Math.min(HEADER.length - header, len);
		if (l > 0) {
			System.arraycopy(HEADER, header, b, off, l);
			header += l;
		}
		return l;
	}

	private int addTrailer(byte[] b, int off, int len) throws IOException {
		if (trailer == -1) {
			makeTrailer();
			trailer = 0;
		}
		int l = Math.min(TRAILER.length - trailer, len);
		if (l > 0) {
			System.arraycopy(TRAILER, trailer, b, off, l);
			trailer += l;
		}
		return l;
	}

	/*
	 * Writes GZIP member header.
	 */

	private final static byte[] HEADER = { (byte) GZIP_MAGIC, // Magic number
			// (short)
			(byte) (GZIP_MAGIC >> 8), // Magic number (short)
			Deflater.DEFLATED, // Compression method (CM)
			0, // Flags (FLG)
			0, // Modification time MTIME (int)
			0, // Modification time MTIME (int)
			0, // Modification time MTIME (int)
			0, // Modification time MTIME (int)
			0, // Extra flags (XFLG)
			0 // Operating system (OS)
	};

	private byte[] TRAILER = new byte[8];

	/*
	 * Writes GZIP member trailer to a byte array, starting at a given offset.
	 */
	private void makeTrailer() throws IOException {
		writeInt((int) crc.getValue(), TRAILER, 0); // CRC-32 of uncompr. data
		writeInt(def.getTotalIn(), TRAILER, 4); // Number of uncompr. bytes
	}

	/*
	 * Writes integer in Intel byte order to a byte array, starting at a given
	 * offset.
	 */
	private void writeInt(int i, byte[] buf, int offset) throws IOException {
		writeShort(i & 0xffff, buf, offset);
		writeShort((i >> 16) & 0xffff, buf, offset + 2);
	}

	/*
	 * Writes short integer in Intel byte order to a byte array, starting at a
	 * given offset
	 */
	private void writeShort(int s, byte[] buf, int offset) throws IOException {
		buf[offset] = (byte) (s & 0xff);
		buf[offset + 1] = (byte) ((s >> 8) & 0xff);
	}

}
